/* src/interfaces/ecpg/preproc/ecpg.c */

/* ecpg的主程序，PostgreSQL嵌入式SQL预编译器。 */
/* Copyright (c) 1996-2022, PostgreSQL Global Development Group */

#include "postgres_fe.h"

#include <unistd.h>

#include "getopt_long.h"

#include "preproc_extern.h"

int			ret_value = 0;
bool		autocommit = false,
			auto_create_c = false,
			system_includes = false,
			force_indicator = true,
			questionmarks = false,
			regression_mode = false,
			auto_prepare = false;

char	   *output_filename;

enum COMPAT_MODE compat = ECPG_COMPAT_PGSQL;

struct _include_path *include_paths = NULL;
struct cursor *cur = NULL;
struct typedefs *types = NULL;
struct _defines *defines = NULL;
struct declared_list *g_declared_list = NULL;

static void fc_help(const char *fc_progname)
{
	printf(_("%s is the PostgreSQL embedded SQL preprocessor for C programs.\n\n"),
		   fc_progname);
	printf(_("Usage:\n"
			 "  %s [OPTION]... FILE...\n\n"),
		   fc_progname);
	printf(_("Options:\n"));
	printf(_("  -c             automatically generate C code from embedded SQL code;\n"
			 "                 this affects EXEC SQL TYPE\n"));
	printf(_("  -C MODE        set compatibility mode; MODE can be one of\n"
			 "                 \"INFORMIX\", \"INFORMIX_SE\", \"ORACLE\"\n"));
#ifdef YYDEBUG
	printf(_("  -d             generate parser debug output\n"));
#endif
	printf(_("  -D SYMBOL      define SYMBOL\n"));
	printf(_("  -h             parse a header file, this option includes option \"-c\"\n"));
	printf(_("  -i             parse system include files as well\n"));
	printf(_("  -I DIRECTORY   search DIRECTORY for include files\n"));
	printf(_("  -o OUTFILE     write result to OUTFILE\n"));
	printf(_("  -r OPTION      specify run-time behavior; OPTION can be:\n"
			 "                 \"no_indicator\", \"prepare\", \"questionmarks\"\n"));
	printf(_("  --regression   run in regression testing mode\n"));
	printf(_("  -t             turn on autocommit of transactions\n"));
	printf(_("  -V, --version  output version information, then exit\n"));
	printf(_("  -?, --help     show this help, then exit\n"));
	printf(_("\nIf no output file is specified, the name is formed by adding .c to the\n"
			 "input file name, after stripping off .pgc if present.\n"));
	printf(_("\nReport bugs to <%s>.\n"), PACKAGE_BUGREPORT);
	printf(_("%s home page: <%s>\n"), PACKAGE_NAME, PACKAGE_URL);
}

static void fc_add_include_path(char *fc_path)
{
	struct _include_path *fc_ip = include_paths,
			   *fc_new;

	fc_new = mm_alloc(sizeof(struct _include_path));
	fc_new->path = fc_path;
	fc_new->next = NULL;

	if (fc_ip == NULL)
		include_paths = fc_new;
	else
	{
		for (; fc_ip->next != NULL; fc_ip = fc_ip->next);
		fc_ip->next = fc_new;
	}
}

/*
 * 处理命令行 -D 选项
 */
static void fc_add_preprocessor_define(char *fc_define)
{
	/* 复制参数以避免依赖于 argv 存储 */
	char	   *fc_define_copy = mm_strdup(fc_define);
	char	   *fc_ptr;
	struct _defines *fc_newdef;

	fc_newdef = mm_alloc(sizeof(struct _defines));

	/* 查找 = 符号 */
	fc_ptr = strchr(fc_define_copy, '=');
	if (fc_ptr != NULL)
	{
		/* 符号有一个值 */
		char	   *fc_tmp;

		/* 去掉名称与 '=' 之间的空格 */
		for (fc_tmp = fc_ptr - 1; fc_tmp >= fc_define_copy && *fc_tmp == ' '; fc_tmp--);
		fc_tmp[1] = '\0';

		/*
		 * 请注意，我们无需单独对 cmdvalue 进行 malloc；
		 * 它永远不会被释放，因此这不是必要的。
		 */
		fc_newdef->cmdvalue = fc_ptr + 1;
	}
	else
	{
		/* 将其定义为 "1"；同样无需 malloc */
		fc_newdef->cmdvalue = "1";
	}
	fc_newdef->name = fc_define_copy;
	fc_newdef->value = mm_strdup(fc_newdef->cmdvalue);
	fc_newdef->used = NULL;
	fc_newdef->next = defines;
	defines = fc_newdef;
}

#define ECPG_GETOPT_LONG_REGRESSION		1
int main(int fc_argc, char *const fc_argv[])
{
	static struct option fc_ecpg_options[] = {
		{"regression", no_argument, NULL, ECPG_GETOPT_LONG_REGRESSION},
		{NULL, 0, NULL, 0}
	};

	int			fc_fnr,
				fc_c,
				fc_out_option = 0;
	bool		fc_verbose = false,
				fc_header_mode = false;
	struct _include_path *fc_ip;
	const char *fc_progname;
	char		fc_my_exec_path[MAXPGPATH];
	char		fc_include_path[MAXPGPATH];

	set_pglocale_pgservice(fc_argv[0], PG_TEXTDOMAIN("ecpg"));

	fc_progname = get_progname(fc_argv[0]);

	if (find_my_exec(fc_argv[0], fc_my_exec_path) < 0)
	{
		fprintf(stderr, _("%s: could not locate my own executable path\n"), fc_argv[0]);
		return ILLEGAL_OPTION;
	}

	if (fc_argc > 1)
	{
		if (strcmp(fc_argv[1], "--help") == 0 || strcmp(fc_argv[1], "-?") == 0)
		{
			fc_help(fc_progname);
			exit(0);
		}
		if (strcmp(fc_argv[1], "--version") == 0 || strcmp(fc_argv[1], "-V") == 0)
		{
			printf("ecpg (PostgreSQL) %s\n", PG_VERSION);
			exit(0);
		}
	}

	output_filename = NULL;
	while ((fc_c = getopt_long(fc_argc, fc_argv, "vcio:I:tD:dC:r:h", fc_ecpg_options, NULL)) != -1)
	{
		switch (fc_c)
		{
			case ECPG_GETOPT_LONG_REGRESSION:
				regression_mode = true;
				break;
			case 'o':
				output_filename = mm_strdup(optarg);
				if (strcmp(output_filename, "-") == 0)
					base_yyout = stdout;
				else
					base_yyout = fopen(output_filename, PG_BINARY_W);

				if (base_yyout == NULL)
				{
					fprintf(stderr, _("%s: could not open file \"%s\": %s\n"),
							fc_progname, output_filename, strerror(errno));
					output_filename = NULL;
				}
				else
					fc_out_option = 1;
				break;
			case 'I':
				fc_add_include_path(optarg);
				break;
			case 't':
				autocommit = true;
				break;
			case 'v':
				fc_verbose = true;
				break;
			case 'h':
				fc_header_mode = true;
				/* FALLTHROUGH */
				/* 这必须包含 "-c" 才有意义，所以继续执行 */
				/************************************************************
	 * 通过其键获取准备好的计划描述符
	 ************************************************************/
			case 'c':
				auto_create_c = true;
				break;
			case 'i':
				system_includes = true;
				break;
			case 'C':
				if (pg_strcasecmp(optarg, "INFORMIX") == 0 || pg_strcasecmp(optarg, "INFORMIX_SE") == 0)
				{
					char		fc_pkginclude_path[MAXPGPATH];
					char		fc_informix_path[MAXPGPATH];

					compat = (pg_strcasecmp(optarg, "INFORMIX") == 0) ? ECPG_COMPAT_INFORMIX : ECPG_COMPAT_INFORMIX_SE;
					get_pkginclude_path(fc_my_exec_path, fc_pkginclude_path);
					snprintf(fc_informix_path, MAXPGPATH, "%s/informix/esql", fc_pkginclude_path);
					fc_add_include_path(fc_informix_path);
				}
				else if (pg_strcasecmp(optarg, "ORACLE") == 0)
				{
					compat = ECPG_COMPAT_ORACLE;
				}
				else
				{
					fprintf(stderr, _("Try \"%s --help\" for more information.\n"), fc_argv[0]);
					return ILLEGAL_OPTION;
				}
				break;
			case 'r':
				if (pg_strcasecmp(optarg, "no_indicator") == 0)
					force_indicator = false;
				else if (pg_strcasecmp(optarg, "prepare") == 0)
					auto_prepare = true;
				else if (pg_strcasecmp(optarg, "questionmarks") == 0)
					questionmarks = true;
				else
				{
					fprintf(stderr, _("Try \"%s --help\" for more information.\n"), fc_argv[0]);
					return ILLEGAL_OPTION;
				}
				break;
			case 'D':
				fc_add_preprocessor_define(optarg);
				break;
			case 'd':
#ifdef YYDEBUG
				base_yydebug = 1;
#else
				fprintf(stderr, _("%s: parser debug support (-d) not available\n"),
						fc_progname);
#endif
				break;
			default:
				fprintf(stderr, _("Try \"%s --help\" for more information.\n"), fc_argv[0]);
				return ILLEGAL_OPTION;
		}
	}

	fc_add_include_path(".");
	fc_add_include_path("/usr/local/include");
	get_include_path(fc_my_exec_path, fc_include_path);
	fc_add_include_path(fc_include_path);
	fc_add_include_path("/usr/include");

	if (fc_verbose)
	{
		fprintf(stderr,
				_("%s, the PostgreSQL embedded C preprocessor, version %s\n"),
				fc_progname, PG_VERSION);
		fprintf(stderr, _("EXEC SQL INCLUDE ... search starts here:\n"));
		for (fc_ip = include_paths; fc_ip != NULL; fc_ip = fc_ip->next)
			fprintf(stderr, " %s\n", fc_ip->path);
		fprintf(stderr, _("end of search list\n"));
		return 0;
	}

	if (optind >= fc_argc)			/* 未指定文件 */
	{
		fprintf(stderr, _("%s: no input files specified\n"), fc_progname);
		fprintf(stderr, _("Try \"%s --help\" for more information.\n"), fc_argv[0]);
		return ILLEGAL_OPTION;
	}
	else
	{
		/* 在选项之后，不能有任何其他内容，只有文件名 */
		for (fc_fnr = optind; fc_fnr < fc_argc; fc_fnr++)
		{
			char	   *fc_ptr2ext;

			/* 如果 argv[fnr] 是 "-"，我们必须从标准输入读取 */
			if (strcmp(fc_argv[fc_fnr], "-") == 0)
			{
				input_filename = mm_alloc(strlen("stdin") + 1);
				strcpy(input_filename, "stdin");
				base_yyin = stdin;
			}
			else
			{
				input_filename = mm_alloc(strlen(fc_argv[fc_fnr]) + 5);
				strcpy(input_filename, fc_argv[fc_fnr]);

				/* 处理相对路径 */
				fc_ptr2ext = last_dir_separator(input_filename);
				fc_ptr2ext = (fc_ptr2ext ? strrchr(fc_ptr2ext, '.') : strrchr(input_filename, '.'));

				/* 没有扩展名？ */
				if (fc_ptr2ext == NULL)
				{
					fc_ptr2ext = input_filename + strlen(input_filename);

					/* 没有扩展名 => 添加 .pgc 或 .pgh */
					fc_ptr2ext[0] = '.';
					fc_ptr2ext[1] = 'p';
					fc_ptr2ext[2] = 'g';
					fc_ptr2ext[3] = (fc_header_mode == true) ? 'h' : 'c';
					fc_ptr2ext[4] = '\0';
				}

				base_yyin = fopen(input_filename, PG_BINARY_R);
			}

			if (fc_out_option == 0)	/* 计算输出名称 */
			{
				if (strcmp(input_filename, "stdin") == 0)
					base_yyout = stdout;
				else
				{
					output_filename = mm_alloc(strlen(input_filename) + 3);
					strcpy(output_filename, input_filename);

					fc_ptr2ext = strrchr(output_filename, '.');
					/* 设置扩展名为 .c 以及 .h */
					fc_ptr2ext[1] = (fc_header_mode == true) ? 'h' : 'c';
					fc_ptr2ext[2] = '\0';

					base_yyout = fopen(output_filename, PG_BINARY_W);
					if (base_yyout == NULL)
					{
						fprintf(stderr, _("%s: could not open file \"%s\": %s\n"),
								fc_progname, output_filename, strerror(errno));
						free(output_filename);
						output_filename = NULL;
						free(input_filename);
						continue;
					}
				}
			}

			if (base_yyin == NULL)
				fprintf(stderr, _("%s: could not open file \"%s\": %s\n"),
						fc_progname, fc_argv[fc_fnr], strerror(errno));
			else
			{
				struct cursor *fc_ptr;
				struct _defines *fc_defptr;
				struct _defines *fc_prevdefptr;
				struct _defines *fc_nextdefptr;
				struct typedefs *fc_typeptr;
				struct declared_list *fc_list;

				/* 如果仍存在旧游标定义，则删除它们 */
				for (fc_ptr = cur; fc_ptr != NULL;)
				{
					struct cursor *fc_this = fc_ptr;
					struct arguments *fc_l1,
							   *fc_l2;

					free(fc_ptr->command);
					free(fc_ptr->connection);
					free(fc_ptr->name);
					for (fc_l1 = fc_ptr->argsinsert; fc_l1; fc_l1 = fc_l2)
					{
						fc_l2 = fc_l1->next;
						free(fc_l1);
					}
					for (fc_l1 = fc_ptr->argsresult; fc_l1; fc_l1 = fc_l2)
					{
						fc_l2 = fc_l1->next;
						free(fc_l1);
					}
					fc_ptr = fc_ptr->next;
					free(fc_this);
				}
				cur = NULL;

				/* 如果仍存在旧声明的语句，则删除它们 */
				for (fc_list = g_declared_list; fc_list != NULL;)
				{
					struct declared_list *fc_this = fc_list;

					fc_list = fc_list->next;
					free(fc_this);
				}

				/* 将定义恢复到其命令行状态 */
				fc_prevdefptr = NULL;
				for (fc_defptr = defines; fc_defptr != NULL; fc_defptr = fc_nextdefptr)
				{
					fc_nextdefptr = fc_defptr->next;
					if (fc_defptr->cmdvalue != NULL)
					{
						/* 保持它，重置值 */
						free(fc_defptr->value);
						fc_defptr->value = mm_strdup(fc_defptr->cmdvalue);
						fc_prevdefptr = fc_defptr;
					}
					else
					{
						/* 删除它 */
						if (fc_prevdefptr != NULL)
							fc_prevdefptr->next = fc_nextdefptr;
						else
							defines = fc_nextdefptr;
						free(fc_defptr->name);
						free(fc_defptr->value);
						free(fc_defptr);
					}
				}

				/* 和旧的 typedefs */
				for (fc_typeptr = types; fc_typeptr != NULL;)
				{
					struct typedefs *fc_this = fc_typeptr;

					free(fc_typeptr->name);
					ECPGfree_struct_member(fc_typeptr->struct_member_list);
					free(fc_typeptr->type);
					fc_typeptr = fc_typeptr->next;
					free(fc_this);
				}
				types = NULL;

				/* 在每当结构时进行初始化 */
				memset(&when_error, 0, sizeof(struct when));
				memset(&when_nf, 0, sizeof(struct when));
				memset(&when_warn, 0, sizeof(struct when));

				/* 和结构成员列表 */
				memset(struct_member_list, 0, sizeof(struct_member_list));

				/*
				 * 以及我们的变量计数器用于超出范围游标的
				 * 变量
				 */
				ecpg_internal_var = 0;

				/* 最终建立实际的连接 */
				connection = NULL;

				/* 初始化词法分析 */
				lex_init();

				/* 我们需要几个包含文件 */
				/* 但如果我们处于头文件模式则不需要 */
				if (regression_mode)
					fprintf(base_yyout, "/* 由 ecpg 处理（回归模式） */\n");
				else
					fprintf(base_yyout, "/* 由 ecpg 处理（%s） */\n", PG_VERSION);

				if (fc_header_mode == false)
				{
					fprintf(base_yyout, "/* 这些包含文件由预处理器添加 */\n#include <ecpglib.h>\n#include <ecpgerrno.h>\n#include <sqlca.h>\n");

					/* 添加一些兼容性头文件 */
					if (INFORMIX_MODE)
						fprintf(base_yyout, "/* 需要用于 informix 兼容性 */\n#include <ecpg_informix.h>\n");

					fprintf(base_yyout, "/* 自动包含部分结束 */\n");
				}

				if (regression_mode)
					fprintf(base_yyout, "#define ECPGdebug(X,Y) ECPGdebug((X)+100,(Y))\n");

				output_line_number();

				/* 解析源文件 */
				base_yyparse();

				/*
				 * 检查是否所有游标确实已打开。
				 * 声明一个游标但不打开它实际上是没有意义的。
				 */
				for (fc_ptr = cur; fc_ptr != NULL; fc_ptr = fc_ptr->next)
					if (!(fc_ptr->opened))
						mmerror(PARSE_ERROR, ET_WARNING, "cursor \"%s\" has been declared but not opened", fc_ptr->name);

				if (base_yyin != NULL && base_yyin != stdin)
					fclose(base_yyin);
				if (fc_out_option == 0 && base_yyout != stdout)
					fclose(base_yyout);

				/*
				 * 如果发生错误，删除输出文件。
				 */
				if (ret_value != 0)
				{
					if (strcmp(output_filename, "-") != 0 && unlink(output_filename) != 0)
						fprintf(stderr, _("could not remove output file \"%s\"\n"), output_filename);
				}
			}

			if (output_filename && fc_out_option == 0)
			{
				free(output_filename);
				output_filename = NULL;
			}

			free(input_filename);
		}
	}
	return ret_value;
}
